package com.cantor.common.util;

import com.cantor.common.serializer.ToBeSerializedData;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.pool.KryoCallback;
import com.esotericsoftware.kryo.pool.KryoFactory;
import com.esotericsoftware.kryo.pool.KryoPool;
import com.esotericsoftware.kryo.serializers.DefaultSerializers;
import com.esotericsoftware.kryo.util.DefaultInstantiatorStrategy;
import de.javakaffee.kryoserializers.*;
import org.objenesis.strategy.StdInstantiatorStrategy;

import java.io.*;
import java.lang.reflect.InvocationHandler;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

/**
 * KryoUtils序列化和反序列化操作
 * 官方文档：
 * 中文：https://blog.csdn.net/fanjunjaden/article/details/72823866
 * 英文：https://github.com/EsotericSoftware/kryo
 */
@Deprecated
public class KryoUtil {

    /* Kryo有三组读写对象的方法
     * 1.如果不知道对象的具体类，且对象可以为null： kryo.writeClassAndObject(output, object); Object object = kryo.readClassAndObject(input);
     * 2.如果类已知且对象可以为null： kryo.writeObjectOrNull(output, someObject); SomeClass someObject = kryo.readObjectOrNull(input, SomeClass.class);
     * 3.如果类已知且对象不能为null:  kryo.writeObject(output, someObject); SomeClass someObject = kryo.readObject(input, SomeClass.class);
     */

    /**
     * （池化Kryo实例）使用ThreadLocal
     */
    private static final ThreadLocal<Kryo> kryos = ThreadLocal.withInitial(() -> {
        Kryo kryo = new Kryo();
        /**
         * 不要轻易改变这里的配置,更改之后，序列化的格式就会发生变化，
         * 上线的同时就必须清除 Redis 里的所有缓存，
         * 否则那些缓存再回来反序列化的时候，就会报错
         */
        //支持对象循环引用（否则会栈溢出）
        kryo.setReferences(true); //默认值就是 true，添加此行的目的是为了提醒维护者，不要改变这个配置
        //不强制要求注册类（注册行为无法保证多个 JVM 内同一个类的注册编号相同；而且业务系统中大量的 Class 也难以一一注册）
        kryo.setRegistrationRequired(false); //默认值就是 false，添加此行的目的是为了提醒维护者，不要改变这个配置
        //Fix the NPE bug when deserializing Collections.
        ((DefaultInstantiatorStrategy) kryo.getInstantiatorStrategy()).setFallbackInstantiatorStrategy(new StdInstantiatorStrategy());
        // 注册序列化器
        kryo.register(Arrays.asList("").getClass(), new ArraysAsListSerializer());
        kryo.register(GregorianCalendar.class, new GregorianCalendarSerializer());
        kryo.register(InvocationHandler.class, new JdkProxySerializer());
        kryo.register(BigDecimal.class, new DefaultSerializers.BigDecimalSerializer());
        kryo.register(BigInteger.class, new DefaultSerializers.BigIntegerSerializer());
        kryo.register(Pattern.class, new RegexSerializer());
        kryo.register(BitSet.class, new BitSetSerializer());
        kryo.register(URI.class, new URISerializer());
        kryo.register(UUID.class, new UUIDSerializer());
        kryo.register(HashMap.class);
        kryo.register(ArrayList.class);
        kryo.register(LinkedList.class);
        kryo.register(HashSet.class);
        kryo.register(TreeSet.class);
        kryo.register(Hashtable.class);
        kryo.register(Date.class);
        kryo.register(java.sql.Date.class);
        kryo.register(Calendar.class);
        kryo.register(ConcurrentHashMap.class);
        kryo.register(SimpleDateFormat.class);
        kryo.register(GregorianCalendar.class);
        kryo.register(LocalDate.class);
        kryo.register(LocalTime.class);
        kryo.register(LocalDateTime.class);
        kryo.register(Timestamp.class);
        kryo.register(Vector.class);
        kryo.register(BitSet.class);
        kryo.register(StringBuffer.class);
        kryo.register(StringBuilder.class);
        kryo.register(Object.class);
        kryo.register(Object[].class);
        kryo.register(String[].class);
        kryo.register(byte[].class);
        kryo.register(char[].class);
        kryo.register(int[].class);
        kryo.register(float[].class);
        kryo.register(double[].class);
        UnmodifiableCollectionsSerializer.registerSerializers(kryo);
        SynchronizedCollectionsSerializer.registerSerializers(kryo);
        return kryo;
    });

    /**
     * （池化Kryo实例）使用KryoPool
     */
    private static KryoFactory factory = Kryo::new;
    private static KryoPool pool = new KryoPool.Builder(factory).softReferences().build();

    /**
     * 使用ThreadLocal创建Kryo
     * 把java对象序列化成byte[];
     *
     * @param obj java对象
     * @return
     */
    public static <T> byte[] serializeObject(T obj) {
        ToBeSerializedData<T> data = new ToBeSerializedData<>(obj);
        ByteArrayOutputStream os = null;
        Output output = null;
        if (null != obj) {
            Kryo kryo = kryos.get();
            try {
                os = new ByteArrayOutputStream();
                output = new Output(os);
                kryo.writeObject(output, data);
                close(output);
                return os.toByteArray();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                close(os);
            }
        }
        return null;
    }

    /**
     * 使用ThreadLocal创建Kryo
     * 把byte[]反序列化成指定的java对象
     *
     * @param bytes
     * @param t     指定的java对象
     * @param <T>
     * @return 指定的java对象
     */
    public static <T> T unSerializeObject(byte[] bytes, Class<T> t) {
        ByteArrayInputStream is = null;
        Input input = null;
        if (null != bytes && bytes.length > 0 && null != t) {
            try {
                Kryo kryo = kryos.get();
                is = new ByteArrayInputStream(bytes);
                input = new Input(is);
                return (T)((ToBeSerializedData) kryo.readObject(input, t)).getTarget();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                close(is);
                close(input);
            }
        }
        return null;
    }


    /**
     * 使用ThreadLocal创建Kryo
     * 把List序列化成byte[];
     *
     * @param list java对象
     * @return
     */
    public static <T> byte[] serializeList(List<T> list) {
        ByteArrayOutputStream os = null;
        Output output = null;
        byte[] bytes = null;
        if (null != list && list.size() > 0) {
            Kryo kryo = kryos.get();
            try {
                os = new ByteArrayOutputStream();
                output = new Output(os);
                kryo.writeObject(output, list);
                close(output);
                bytes = os.toByteArray();
                return bytes;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                close(os);
            }
        }
        return null;
    }

    /**
     * 使用ThreadLocal创建Kryo
     * 把byte[]反序列化成指定的List<T>
     *
     * @param bytes byte数组
     * @param <T>
     * @return 指定java对象的List
     */
    public static <T> List<T> unSerializeList(byte[] bytes) {
        ByteArrayInputStream is = null;
        Input input = null;
        if (null != bytes && bytes.length > 0) {
            try {
                Kryo kryo = kryos.get();
                is = new ByteArrayInputStream(bytes);
                input = new Input(is);
                List<T> list = kryo.readObject(input, ArrayList.class);
                return list;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                close(is);
                close(input);
            }
        }
        return null;
    }


    /**
     * 使用ThreadLocal创建Kryo
     * 把java对象转序列化存储在文件中;
     *
     * @param obj java对象
     * @return
     */
    public static <T> boolean serializeFile(T obj, String path) {
        if (null != obj) {
            Output output = null;
            try {
                Kryo kryo = kryos.get();
                output = new Output(new FileOutputStream(path));
                kryo.writeObject(output, obj);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                close(output);
            }
        }
        return false;
    }

    /**
     * 使用ThreadLocal创建Kryo
     * 把序列化的文件反序列化成指定的java对象
     *
     * @param path 文件路径
     * @param t    指定的java对象
     * @param <T>
     * @return 指定的java对象
     */
    public static <T> T unSerializeFile(String path, Class<T> t) {
        if (null != path && null != t) {
            Input input = null;
            try {
                Kryo kryo = kryos.get();
                input = new Input(new FileInputStream(path));
                return kryo.readObject(input, t);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                close(input);
            }
        }
        return null;
    }

    /**
     * 使用KryoPool SoftReferences创建Kryo
     * 把java对象序列化成byte[] ;
     *
     * @param obj java对象
     * @return
     */
    public static <T> byte[] serializePoolSoftReferences(T obj) {
        if (null != obj) {
            Kryo kryo = pool.borrow();
            ByteArrayOutputStream os = null;
            Output output = null;
            try {
                os = new ByteArrayOutputStream();
                output = new Output(os);
                kryo.writeObject(output, obj);
                close(output);
                byte[] bytes = os.toByteArray();
                return bytes;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                pool.release(kryo);
                close(os);
            }
        }
        return null;
    }

    /**
     * 使用KryoPool SoftReferences创建Kryo
     * 把byte[]反序列化成指定的java对象
     *
     * @param bytes
     * @return
     */
    public static <T> T unSerializePoolSoftReferences(byte[] bytes, Class<T> t) {
        if (null != bytes && bytes.length > 0 && null != t) {
            Kryo kryo = pool.borrow();
            ByteArrayInputStream is = null;
            Output output = null;
            try {
                is = new ByteArrayInputStream(bytes);
                Input input = new Input(is);
                return kryo.readObject(input, t);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                pool.release(kryo);
                close(is);
                close(output);
            }
        }
        return null;
    }


    /**
     * 使用KryoPool SoftReferences创建Kryo
     * 把java对象序列化成byte[] ;
     *
     * @param obj java对象
     * @return
     */
    public static <T> byte[] serializePoolCallback(final T obj) {
        if (null != obj) {
            try {
                return pool.run(new KryoCallback<byte[]>() {
                    public byte[] execute(Kryo kryo) {
                        ByteArrayOutputStream os = new ByteArrayOutputStream();
                        Output output = new Output(os);
                        kryo.writeObject(output, obj);
                        output.close();
                        try {
                            os.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        return os.toByteArray();
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 使用KryoPool SoftReferences创建Kryo
     * 把byte[]反序列化成指定的java对象
     *
     * @param bytes
     * @return
     */
    public static <T> T unSerializePoolCallback(final byte[] bytes, final Class<T> t) {
        if (null != bytes && bytes.length > 0 && null != t) {
            try {
                return pool.run(new KryoCallback<T>() {
                    public T execute(Kryo kryo) {
                        ByteArrayInputStream is = new ByteArrayInputStream(bytes);
                        Input input = new Input(is);
                        T result = kryo.readObject(input, t);
                        input.close();
                        try {
                            is.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        return result;
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 关闭io流对象
     *
     * @param closeable
     */
    public static void close(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


}
